//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "Graphics/Resources/Texture.h"

namespace Engine
{
namespace Graphics
{
	
/*
=================================================
	constructor
=================================================
*/
	Texture::Texture (const EngineSubSystemsRef ess, gl_texture::type target) :
		Resource( EResource::TEXTURE, ess ),
		_state( target ), _format(EPixelFormat::_UNKNOWN), _dim(), _levels(0)
	{}
		
/*
=================================================
	destructor
=================================================
*/
	Texture::~Texture ()
	{
		_Destroy();
	}
	
/*
=================================================
	Destroy
=================================================
*/
	void Texture::Destroy ()
	{
		_Destroy();
	}
	
/*
=================================================
	SetImage
=================================================
*/
	bool Texture::SetImage (const uvec3 &dim, EPixelFormat::type format, uint level, const uni_binbuffer_t &data)
	{
		_Create();
		_BindOrActive();
		
		if ( _levels.IsZero() )
		{
			_dim	= dim << level;
			_format	= format;
		}

		_levels.Set( level, true );

		ASSERT( _format == format );

		switch ( Target() )
		{
			case gl_texture::TEXTURE_2D :	return _SetImage2D( dim.xy(), format, level, data );
			case gl_texture::CUBEMAP	:	return _SetImageCube( dim.xy(), dim.z, format, level, data );
		};

		RETURN_ERR( "unsupported texture type" );
	}
	
/*
=================================================
	AddImage
=================================================
*/
	bool Texture::AddImage (const uvec3 &offset, const uvec3 &dim, EPixelFormat::type format, uint level, const uni_binbuffer_t &data)
	{
		CHECK_ERR( not data.Empty() );
		CHECK_ERR( Id() != 0 );

		CHECK( _levels.Get( level ) && "texture level is not allocated" );

		_BindOrActive();

		if ( not _levels.Get( level ) )
			AllocLevel( level );
		
		ASSERT( _format == format );

		switch ( Target() )
		{
			case gl_texture::TEXTURE_2D :	return _SetSubImage2D( offset.xy(), dim.xy(), level, format, data );
			case gl_texture::CUBEMAP	:	return _SetSubImageCube( offset.xy(), dim.xy(), offset.z, level, format, data );
		};

		RETURN_ERR( "unsupported texture type" );
	}
	
/*
=================================================
	Alloc
=================================================
*/
	bool Texture::Alloc (const uvec3 &dim, EPixelFormat::type format, uint levels)
	{
		CHECK_ERR( levels > 0 );

		_Create();
		_BindOrActive();

		_dim	= dim;
		_format	= format;

		_levels.SetInterval( 0, Max<int>( levels-1, 0 ) );

		switch ( Target() )
		{
			case gl_texture::TEXTURE_2D :	return _Storage2D( dim.xy(), format, levels );
			case gl_texture::CUBEMAP	:	return _StorageCube( dim.xy(), format, levels );
		};

		RETURN_ERR( "unsupported texture type" );
	}
	
/*
=================================================
	AllocLevel
=================================================
*/
	bool Texture::AllocLevel (uint level)
	{
		CHECK_ERR( _levels.IsZero() );
		
		_BindOrActive();
		_levels.Set( level, true );

		switch ( Target() )
		{
			case gl_texture::TEXTURE_2D :	return _AllocLevel2D( level );
			case gl_texture::CUBEMAP	:	return _AllocLevelCube( level );
		};

		RETURN_ERR( "unsupported texture type" );
	}
	
/*
=================================================
	GenerateMipmaps
=================================================
*/
	bool Texture::GenerateMipmaps ()
	{
		CHECK_ERR( _levels.Get(0) && "zero level must be created" );

		using namespace gles;

		_BindOrActive();

		_levels.SetInterval( 0, Max<int>( MaxLevels()-1, 0 ) );

		GL_CALL_R( glGenerateMipmap( Target() ), false );
		return true;
	}
	
/*
=================================================
	SwapData
=================================================
*/
	bool Texture::SwapData (const ResourcePtr &res)
	{
		CHECK_ERR( Resource::SwapData( res ) );

		TexturePtr tex = (TexturePtr &)res;

		_swap( this->_sampler,	tex->_sampler );
		_swap( this->_state,	tex->_state );
		_swap( this->_format,	tex->_format );
		_swap( this->_dim,		tex->_dim );
		_swap( this->_levels,	tex->_levels );

		return true;
	}
	
/*
=================================================
	_SetImage2D
=================================================
*/
	bool Texture::_SetImage2D (const uvec2 &dim, EPixelFormat::type format, uint level, const uni_binbuffer_t &data)
	{
		using namespace gles;

		bool comp = EPixelFormat::Utils::IsCompressed( format );

		if ( comp )
		{
			// 4x4 block
			CHECK_ERR( All( dim == Align<4>(dim) ) );
			CHECK_ERR( not data.Empty() );

			GLenum ifmt = 0;

			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( glCompressedTexImage2D( Target(), level, ifmt, dim.x, dim.y, 0, data.Size(), data.ptr() ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;

			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );
			GL_CALL_R( glTexImage2D( Target(), level, fmt, dim.x, dim.y, 0, fmt, type, data.ptr() ), false );
		}

		return true;
	}
	
/*
=================================================
	_SetSubImage2D
=================================================
*/
	bool Texture::_SetSubImage2D (const uvec2 &offset, const uvec2 &dim, uint level, EPixelFormat::type format, const uni_binbuffer_t &data)
	{
		using namespace gles;

		bool comp = EPixelFormat::Utils::IsCompressed( format );

		if ( comp )
		{
			// 4x4 block
			CHECK_ERR( All( offset == Align<4>(offset) ) );
			CHECK_ERR( All( dim == Align<4>(dim) ) );

			GLenum ifmt = 0;
			
			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( glCompressedTexSubImage2D( Target(), level, offset.x, offset.y, dim.x, dim.y, ifmt, data.Size(), data.ptr() ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;
			
			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );
			GL_CALL_R( glTexSubImage2D( Target(), level, offset.x, offset.y, dim.x, dim.y, fmt, type, data.ptr() ), false );
		}

		return true;
	}
	
/*
=================================================
	_Storage2D
=================================================
*/
	bool Texture::_Storage2D (const uvec2 &dim, EPixelFormat::type format, uint levels)
	{
		using namespace gles;

		if ( false ) //EXT_texture_storage::IsSupported() )
		{
			GLenum	ifmt = 0;
			
			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( EXT_texture_storage::glTexStorage2D( Target(), levels, ifmt, dim.x, dim.y ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;
			
			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );

			for (uint i = 0; i < levels; ++i)
			{
				GL_CALL_R( glTexImage2D( Target(), i, fmt, Max( dim.x >> i, 1u ), Max( dim.y >> i, 1u ), 0, fmt, type, null ), false );
			}
		}
		return true;
	}
	
/*
=================================================
	_AllocLevel2D
=================================================
*/
	bool Texture::_AllocLevel2D (uint level)
	{
		using namespace gles;

		GLenum	fmt  = 0;
		GLenum	type = 0;
		
		CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, _format ) );

		uint	w = Max( _dim.x >> level, 1u );
		uint	h = Max( _dim.y >> level, 1u );

		GL_CALL_R( glTexImage2D( Target(), level, fmt, w, h, 0, fmt, type, null ), false );
		return true;
	}
	
/*
=================================================
	_SetImageCube
=================================================
*/
	bool Texture::_SetImageCube (const uvec2 &dim, uint face, EPixelFormat::type format, uint level, const uni_binbuffer_t &data)
	{
		using namespace gles;

		CHECK_ERR( face < 6 );

		bool	comp	= EPixelFormat::Utils::IsCompressed( format );
		GLenum	target	= gl_texture::CUBEFACE_POS_X + face;

		if ( comp )
		{
			// 4x4 block
			CHECK_ERR( All( dim == Align<4>(dim) ) );
			CHECK_ERR( not data.Empty() );

			GLenum ifmt = 0;
			
			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( glCompressedTexImage2D( target, level, ifmt, dim.x, dim.y, 0, data.Size(), data.ptr() ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;
			
			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );
			GL_CALL_R( glTexImage2D( target, level, fmt, dim.x, dim.y, 0, fmt, type, data.ptr() ), false );
		}

		return true;
	}
	
/*
=================================================
	_SetSubImageCube
=================================================
*/
	bool Texture::_SetSubImageCube (const uvec2 &offset, const uvec2 &dim, uint face, uint level, EPixelFormat::type format, const uni_binbuffer_t &data)
	{
		using namespace gles;

		bool	comp	= EPixelFormat::Utils::IsCompressed( format );
		GLenum	target	= gl_texture::CUBEFACE_POS_X + face;

		if ( comp )
		{
			// 4x4 block
			CHECK_ERR( All( offset == Align<4>(offset) ) );
			CHECK_ERR( All( dim == Align<4>(dim) ) );

			GLenum ifmt = 0;
			
			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( glCompressedTexSubImage2D( target, level, offset.x, offset.y, dim.x, dim.y, ifmt, data.Size(), data.ptr() ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;
			
			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );
			GL_CALL_R( glTexSubImage2D( target, level, offset.x, offset.y, dim.x, dim.y, fmt, type, data.ptr() ), false );
		}

		return true;
	}
	
/*
=================================================
	_StorageCube
=================================================
*/
	bool Texture::_StorageCube (const uvec2 &dim, EPixelFormat::type format, uint levels)
	{
		using namespace gles;

		if ( EXT_texture_storage::IsSupported() )
		{
			GLenum	ifmt = 0;
			
			CHECK_ERR( EPixelFormat::GetCompressedTexFormat( ifmt, format ) );
			GL_CALL_R( EXT_texture_storage::glTexStorage2D( Target(), levels, ifmt, dim.x, dim.y ), false );
		}
		else
		{
			GLenum	fmt  = 0;
			GLenum	type = 0;
			
			CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, format ) );

			for (uint i = 0; i < levels; ++i)
			{
				const uint	w = Max( dim.x >> i, 1u );
				const uint	h = Max( dim.y >> i, 1u );

				for (uint face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++face )
					GL_CALL_R( glTexImage2D( face, i, fmt, w, h, 0, fmt, type, null ), false );
			}
		}
		return true;
	}
	
/*
=================================================
	_AllocLevelCube
=================================================
*/
	bool Texture::_AllocLevelCube (uint level)
	{
		using namespace gles;

		GLenum	fmt  = 0;
		GLenum	type = 0;
		
		CHECK_ERR( EPixelFormat::GetTexDataFormat( fmt, type, _format ) );

		uint	w = Max( _dim.x >> level, 1u );
		uint	h = Max( _dim.y >> level, 1u );
		
		for (uint face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++face )
			GL_CALL_R( glTexImage2D( face, level, fmt, w, h, 0, fmt, type, null ), false );

		return true;
	}
	
/*
=================================================
	_Create
=================================================
*/
	bool Texture::_Create()
	{
		using namespace gles;

		if ( _state.id != 0 )
			return true;

		GL_CALL_R( glGenTextures( 1, &_state.id ), false );

		Bind( 0 );
		_sampler.Bind( Target() );
		return true;
	}
	
/*
=================================================
	_Destroy
=================================================
*/
	void Texture::_Destroy()
	{
		using namespace gles;

		if ( IsBinded() ) {
			ESS()->GetGLStateManager()->UnbindTexture( _state );
		}
		if ( _state.id != 0 ) {
			GL_CALL( glDeleteTextures( 1, &_state.id ) );
		}

		_state		= binded_state_t( gl_texture::TEXTURE_2D );
		_sampler	= Sampler();
		_format		= EPixelFormat::_UNKNOWN;
		_dim		= uvec3();
		_levels		= 0;
	}

/*
=================================================
	IsLoadedWithParams
=================================================
*/
	bool Texture::IsLoadedWithParams (const ResLoadParams &params) const
	{
		// TODO
		return Resource::IsLoadedWithParams( params );
	}
	
/*
=================================================
	IsValid
=================================================
*/
	bool Texture::IsValid () const
	{
		return Id() != 0;
	}

/*
=================================================
	New
=================================================
*/
	TexturePtr Texture::New (const EngineSubSystemsRef ess, gl_texture::type target)
	{
		return BaseObject::_New( new Texture( ess, target ) );
	}
	
/*
=================================================
	GetDefaultLoadParams
=================================================
*/
	Texture::TexLoadParams Texture::GetDefaultLoadParams ()
	{
		TexLoadParams	params;

		return params;
	}
	

}	// Graphics
}	// Engine